/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.scripting.xmltags;

import java.util.HashMap;
import java.util.Map;

import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;

import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;

/**
 * @author Clinton Begin
 */
/**
 * 动态上下文
 * 
 */
public class DynamicContext {
	// 在编写映射文件时, '${_parameter}','${_databaseId}'分别可以取到当前用户传入的参数, 以及当前执行的数据库类型
	/**
	 * 参数
	 */
	public static final String PARAMETER_OBJECT_KEY = "_parameter";
	/**
	 * 数据库类型
	 */
	public static final String DATABASE_ID_KEY = "_databaseId";

	static {
		// TODO OgnlRuntime
		// 定义属性->getter方法映射，ContextMap到ContextAccessor的映射，注册到ognl运行时
		// 参考http://commons.apache.org/proper/commons-ognl/developer-guide.html

		// Mybatis中采用了Ognl来计算动态sql语句，DynamicContext类中的这个静态初始块，很好的说明了这一点
		OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
		// 将传入的参数对象统一封装为ContextMap对象（继承了HashMap对象），
		// 然后Ognl运行时环境在动态计算sql语句时，
		// 会按照ContextAccessor中描述的Map接口的方式来访问和读取ContextMap对象，获取计算过程中需要的参数。
		// ContextMap对象内部可能封装了一个普通的POJO对象，也可以是直接传递的Map对象，当然从外部是看不出来的，因为都是使用Map的接口来读取数据。
	}
	/**
	 * 绑定
	 */
	private final ContextMap bindings;
	/**
	 * Sql构建器
	 */
	private final StringBuilder sqlBuilder = new StringBuilder();
	/**
	 * 独特的数量
	 */
	private int uniqueNumber = 0;

	/**
	 * 在DynamicContext的构造函数中，根据传入的参数对象是否为Map类型，有两个不同构造ContextMap的方式。
	 * 而ContextMap作为一个继承了HashMap的对象，作用就是用于统一参数的访问方式：用Map接口方法来访问数据。
	 * 具体来说，当传入的参数对象不是Map类型时，Mybatis会将传入的POJO对象用MetaObject对象来封装，
	 * 当动态计算sql过程需要获取数据时，用Map接口的get方法包装 MetaObject对象的取值过程。
	 * 
	 * @param configuration
	 * @param parameterObject
	 */
	public DynamicContext(Configuration configuration, Object parameterObject) {
		// 绝大多数调用的地方parameterObject为null
		// 判断parameterObject是否为空并且是Map类型
		if (parameterObject != null && !(parameterObject instanceof Map)) {
			// 如果是map型
			MetaObject metaObject = configuration.newMetaObject(parameterObject);
			bindings = new ContextMap(metaObject);
		} else {
			// 当用户传入的参数null或Map类型时
			bindings = new ContextMap(null);
		}
		// 向刚构造出来的ContextMap实例中推入用户本次传入的参数parameterObject
		bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
		// 向刚构造出来的ContextMap实例中推入用户配置的DatabaseId.
		bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
	}
	/**
	 * 得到绑定的Map
	 * @return
	 */
	public Map<String, Object> getBindings() {
		return bindings;
	}
	/**
	 * 添加绑定元素
	 * @param name
	 * @param value
	 */
	public void bind(String name, Object value) {
		bindings.put(name, value);
	}
	/**
	 * sql追加sql
	 * @param sql
	 */
	public void appendSql(String sql) {
		sqlBuilder.append(sql);
		sqlBuilder.append(" ");
	}
	/**
	 * 得到Sql
	 * @return
	 */
	public String getSql() {
		return sqlBuilder.toString().trim();
	}
	/**
	 * 得到独特的号码
	 * @return
	 */
	public int getUniqueNumber() {
		return uniqueNumber++;
	}

	/**
	 * 上下文map，静态内部类
	 * 
	 * @author
	 */
	static class ContextMap extends HashMap<String, Object> {
		private static final long serialVersionUID = 2977601501966151582L;
		/**
		 * 参数元数据对象
		 */
		private MetaObject parameterMetaObject;

		/**
		 * 初始化参数元数据对象
		 * 
		 * @param parameterMetaObject
		 */
		public ContextMap(MetaObject parameterMetaObject) {
			this.parameterMetaObject = parameterMetaObject;
		}

		/**
		 * 通过key取value
		 */
		@Override
		public Object get(Object key) {
			String strKey = (String) key;
			// 先去map里找
			if (super.containsKey(strKey)) {
				return super.get(strKey);
			}

			// 如果没找到，再用ognl表达式去取值
			// 如person[0].birthdate.year
			if (parameterMetaObject != null) {
				// issue #61 do not modify the context when reading
				// 从元数据取得key对应的值
				return parameterMetaObject.getValue(strKey);
			}
			return null;
		}
	}

	/**
	 * 上下文访问器，静态内部类,实现OGNL的PropertyAccessor
	 * 
	 * @author
	 *
	 */
	static class ContextAccessor implements PropertyAccessor {
		/**
		 * 得到参数
		 */
		@Override
		public Object getProperty(Map context, Object target, Object name) throws OgnlException {
			// target为ContextMap,所以可以安全地转换为Map
			Map map = (Map) target;

			// 这里调用的ContextMap覆写的get方法;也就是缓存的POJO中的属性对;
			// 如果你要是在其中取 PARAMETER_OBJECT_KEY
			// 对应的对象,返回的result就是用户传入的parameterObject.
			// 这里不为null时, 说明用户传入的是POJO
			Object result = map.get(name);
			if (result != null) {
				return result;
			}
			// --- 这里就是处理当用户传入的参数是Map时
			// 构造DynamicContext实例时,插入到ContextMap实例中的键值对:{ "_parameter" :
			// parameterObject }
			Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
			// 用户显式传入的就是Map类型
			if (parameterObject instanceof Map) {
				return ((Map) parameterObject).get(name);
			}

			return null;
		}

		@Override
		public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException {
			Map<Object, Object> map = (Map<Object, Object>) target;
			map.put(name, value);
		}

		@Override
		public String getSourceAccessor(OgnlContext arg0, Object arg1, Object arg2) {
			return null;
		}

		@Override
		public String getSourceSetter(OgnlContext arg0, Object arg1, Object arg2) {
			return null;
		}
	}
}